//@author A0096759E

package googlecal;

import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;

import javax.net.ssl.HttpsURLConnection;

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;

import storage.ScheduleEvent;
import storage.Storage;
import applogger.AppLogger;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class GoogleSynchronizer implements Runnable{
	
	private static final String GOOGLE_LIFESHELF_URL = "https://www.googleapis.com/calendar/v3/calendars/%s/events";
	private static final String GOOGLE_CALENDAR_CREATE_CAL = "https://www.googleapis.com/calendar/v3/calendars";
	private static final String GOOGLE_CALENDAR_CREATE_CAL_QUERY = "{\"summary\": \"LifeShelf\",\"etag\": \"\",\"timeZone\": \"Singapore\"}";
	private static final String GOOGLE_GET_CALENDAR_URL = "https://www.googleapis.com/calendar/v3/calendars/%s";
	private static final String GOOGLE_CALENDAR_INSERT_EVENT = "https://www.googleapis.com/calendar/v3/calendars/%s/events";
	private static final String GOOGLE_CALENDAR_UPDATE_EVENT = "https://www.googleapis.com/calendar/v3/calendars/%s/events/%s";
	private static final String GOOGLE_DELETE_ITEM_URL = "https://www.googleapis.com/calendar/v3/calendars/%s/events/%s";

	private static final String GOOGLE_TASKS_URL = "https://www.googleapis.com/tasks/v1/lists/%s/tasks";
	private static final String GOOGLE_TASK_CREATE_TASKLIST = "https://www.googleapis.com/tasks/v1/users/@me/lists";
	private static final String GOOGLE_TASK_CREATE_TASKLIST_QUERY = "{'title': 'LifeShelfTaskList'}";
	private static final String GOOGLE_GET_TASKLIST_URL = "https://www.googleapis.com/tasks/v1/users/@me/lists/%s";
	private static final String GOOGLE_TASK_INSERT_TASK = "https://www.googleapis.com/tasks/v1/lists/%s/tasks";
	private static final String GOOGLE_TASK_UPDATE_TASK = "https://www.googleapis.com/tasks/v1/lists/%s/tasks/%s";
	private static final String GOOGLE_TASK_DELETE_ITEM_URL = "https://www.googleapis.com/tasks/v1/lists/%s/tasks/%s";
	private static final String GOOGLE_URL = "http://google.com.sg";

	private static final int EVENT_TYPE_TIMED = 2;
	private static final String ENCODING_UTF8 = "UTF-8";
	private static final String STRING_EMPTY = "";

	private static final String REQUEST_TYPE_GET = "GET";
	private static final String REQUEST_TYPE_POST = "POST";
	private static final String REQUEST_TYPE_DELETE = "DELETE";
	private static final String REQUEST_TYPE_PUT = "PUT";
	private static final String REQUEST_CONTENT_TYPE = "Content-Type";
	private static final String REQUEST_CONTENT_TYPE_JSON = "application/json";
	private static final String REQUEST_PROPERTY_AUTH = "Authorization";
	private static final String REQUEST_AUTH_INPUT = "Bearer %s";
	
	private static final String DATETIME_FORMAT_GTASK_DATETIME = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
	private static final String DATETIME_FORMAT_GCAL_DATETIME = "yyyy-MM-dd'T'HH:mm:ss'Z'";

	private static final String LOGGER_CONNECTION_RESET = "Connection was lost!";
	private static final String LOGGER_CONNECTION_RESUMED = "Connection has resumed successfully!";
	private static final String LOGGER_EVENT_GOT_NO_GUID = "%s got no GUID!";
	private static final String LOGGER_ADD_ITEM_TO_TASKLIST = "Adding item \"%s\" to TASK LIST!";
	private static final String LOGGER_ADD_ITEM_TO_GCAL = "Adding item \"%s\" to GOOGLE CALENDAR!";
	private static final String LOGGER_NEED_TO_DELETE_ITEM = "Need to delete! %s";
	
	private static final String LOGGER_CHECK_CALENDAR_RESPONSE_CODE = "Check Existence of Calendar Response Code : %s";
	private static final String LOGGER_CREATE_NEW_LIFESHELF_CAL_RESPONSE_CODE = "Create LifeShelf Calendar Response Code : %s";
	private static final String LOGGER_CREATE_LIFESHELF_TASKLIST_RESPONSE_CODE = "Create LifeShelf TaskList Response Code : %s";
	private static final String LOGGER_GET_CALENDAR_ITEMS_RESPONSE_CODE = "Get Calendar Response Code : %s";
	private static final String LOGGER_DELETE_GCAL_ITEM_RESPONSE_CODE = "Delete GCal Item Response Code : %s";
	
	private static final String LOGGER_UPDATE_GTASK_RESPONSE_CODE = "Update GTask LifeShelf Response Code : %s";
	private static final String LOGGER_ADD_EVENT_TO_GTASKS_RESPONSE_CODE = "Add Event To GTASKS LifeShelf Response Code : ";
	private static final String LOGGER_DELETE_GTASK_ITEM_RESPONSE_CODE = "Delete GTask Item Response Code : %s";
	private static final String LOGGER_GET_TASKLIST_RESPONSE_CODE = "Get TaskList Response Code : %s";
	private static final String LOGGER_CHECK_EXISTENCE_OF_TASKLIST_RESPONSE_CODE = "Check Existence of TaskList Response Code : %s";
	private static final String LOGGER_UPDATE_GCAL_RESPONSE_CODE = "Update Gcal LifeShelf Response Code : %s";
	private static final String LOGGER_ADD_EVENT_TO_GCAL_RESPONSE_CODE = "Add Event To GCal LifeShelf Response Code : %s";
	
	private String accessToken = "nil";
	private String googleCalendarLifeShelfID = "inexistent";
	private String googleTasksLifeShelfID = "inexistent";
	private Storage eventsList;
	private ScheduleEvent[] googleCalendarItems;
	private ScheduleEvent[] googleTaskItems;
	private ArrayList<ScheduleEvent> cloneOfStorage;
	
	/**
	 * Constructor of GoogleSynchronizer, which synchronizes local information with Google
	 * Calendar and Google Tasks.
	 * @param accessToken - validated access token to access API
	 * @param eventsList - Storage Element of LifeShelf
	 */
	public GoogleSynchronizer(String accessToken, Storage eventsList) {
		this.accessToken = accessToken;
		this.eventsList = eventsList;
	}
	
	@Override
	public void run() {
		try {
			googleCalendarLifeShelfID = GoogleAPIInternalStorage.readPreviousGoogleCalID();
			googleTasksLifeShelfID = GoogleAPIInternalStorage.readPreviousGoogleTaskID();
			assert(googleCalendarLifeShelfID != null);
			while (true) {
				if (checkExistenceOfCalendar() != 200) {
					this.createLifeShelfGoogleCalendar();
				}
				googleCalendarItems = this.getCalendarItems().getGoogleCalendarItems();
				
				if (checkExistenceOfTaskList() != 200) {
					this.createGoogleTaskList();
				}
				googleTaskItems = this.getGoogleTaskItems().getGoogleCalendarItems();
				
				this.updateGoogleLifeShelfCalendar();
				Thread.sleep(10000);
			}
			
		} catch (Exception e) {
			e.printStackTrace();
			AppLogger.log(LOGGER_CONNECTION_RESET);
			while (!isInternetReachable()) {}
			AppLogger.log(LOGGER_CONNECTION_RESUMED);
			run();
		}
	}
	
	/**
	 * Checks whether internet connection is available
	 * @return a boolean whether internet is available.
	 */
	public static boolean isInternetReachable() {
         try {
             URL url = new URL(GOOGLE_URL);
             HttpURLConnection urlConnect = (HttpURLConnection)url.openConnection();
             urlConnect.getContent();
         } catch (Exception e) {
             return false;
         }
         return true;
     }

	
	/**
	 * Algorithm that synchronizes local data with the Google cloud data. 
	 * 
	 * It starts by looping through a clone of the Local LifeShelf Storage.
	 * 
	 * Then, loop through Google Calendar Items array.
	 * 
	 * Then we do the same thing with Google Tasks.
	 */
	private void updateGoogleLifeShelfCalendar() {
		cloneOfStorage = eventsList.getStorageClone();
		try {
			updateLoopThroughLocal();
			updateLoopThroughGCal();
			updateLoopThroughGTask();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * This method loops through a clone of the Local LifeShelf Storage. For each item,
	 * if it has no Google UID, it is pushed up to Google Calendar (timed task) or Google Tasks
	 * (deadline and floating task). If it has a Google UID, it is checked against the list of
	 * Google Calendar and Google Task items to see if there is a match in Google UID. We check
	 * if both events are equal. If they are not, we take the latest copy and update the other.
	 * If it has a Google UID but is not found in either list, it is deleted from the local storage.
	 * @throws Exception
	 */
	private void updateLoopThroughLocal() throws Exception {
		ArrayList<ScheduleEvent> localItemsToDelete = new ArrayList<ScheduleEvent>();
		for (int i = 0; i < cloneOfStorage.size(); i++) {
			ScheduleEvent localScheduleEvent = cloneOfStorage.get(i);
			if (localScheduleEvent.getGoogleUID() == null) {
				AppLogger.log(String.format(LOGGER_EVENT_GOT_NO_GUID, localScheduleEvent.getName()));
				String googleUID;
				if (localScheduleEvent.getType() == EVENT_TYPE_TIMED) {
					AppLogger.log(String.format(LOGGER_ADD_ITEM_TO_GCAL, localScheduleEvent.getName()));
					googleUID = this.addEventToLifeShelfCalendar(localScheduleEvent);
				} else {
					AppLogger.log(String.format(LOGGER_ADD_ITEM_TO_TASKLIST, localScheduleEvent.getName()));
					googleUID = this.addEventToLifeShelfTaskList(localScheduleEvent);
				}
				localScheduleEvent.setGoogleUID(googleUID);
			} else {
				ScheduleEvent googleCalScheduleEvent = getGoogleCalItemById(localScheduleEvent.getUID());
				ScheduleEvent googleTaskScheduleEvent = getGoogleTaskItemById(localScheduleEvent.getUID());
				if (googleCalScheduleEvent != null) {
					if (!localScheduleEvent.equals(googleCalScheduleEvent)) {
						//update
						if (getUpdatedTime(localScheduleEvent).isAfter(getUpdatedTime(googleCalScheduleEvent))) {
							updateGoogleCalendarEvent(localScheduleEvent);
						} else {
							updateLocalEvent(localScheduleEvent, googleCalScheduleEvent);
						}
					}
				} else if (googleTaskScheduleEvent != null) {
					if (!localScheduleEvent.equals(googleTaskScheduleEvent)) {
						//update
						if (getUpdatedTime(localScheduleEvent).isAfter(getUpdatedTime(googleTaskScheduleEvent))) {
							updateGoogleTaskListEvent(localScheduleEvent);
						} else {
							updateLocalEvent(localScheduleEvent, googleTaskScheduleEvent);
						}
					}
				} else {
					localItemsToDelete.add(localScheduleEvent);
				}
			}
		}
		
		for (int i = 0; i < localItemsToDelete.size(); i++) {
			AppLogger.log(eventsList.deleteEvent(eventsList.getEventIndex(localItemsToDelete.get(i)) + 1));
		}
	}
	/**
	 * This method loops through Google Calendar Items array. If no local UID, add to local storage. If have
	 * local UID, we check to see if it exists in the local storage. If does not exist, we delete from
	 * Google Calendar.
	 * @throws Exception
	 */
	private void updateLoopThroughGCal() throws Exception {
		ArrayList<ScheduleEvent> googleCalItemsToDelete = new ArrayList<ScheduleEvent>();
		googleCalendarItems = this.getCalendarItems().getGoogleCalendarItems();
		if (googleCalendarItems != null) {
			for (int i = 0; i < googleCalendarItems.length; i++) {
				ScheduleEvent googleScheduleEvent = googleCalendarItems[i];
				if (googleScheduleEvent.getUID() == null) {
					AppLogger.log(eventsList.addEvent(googleScheduleEvent));
					updateGoogleCalendarEvent(googleScheduleEvent);
				} else {
					ScheduleEvent localScheduleEvent = getEventByGoogleID(googleScheduleEvent.getGoogleUID()); 
					if (localScheduleEvent == null || localScheduleEvent.getType() != EVENT_TYPE_TIMED) {
						AppLogger.log(String.format(LOGGER_NEED_TO_DELETE_ITEM, googleScheduleEvent.getName()));
						googleCalItemsToDelete.add(googleScheduleEvent);
					}
				}
			}
			
			for (int i = 0; i < googleCalItemsToDelete.size(); i++) {
				deleteGoogleCalendarItem(googleCalItemsToDelete.get(i).getGoogleUID());
			}
		}
	}
	

	/**
	 * This method loops through Google Tasks Items array. If no local UID, add to local storage. If have
	 * local UID, we check to see if it exists in the local storage. If does not exist, we delete from
	 * Google Tasks.
	 * @throws Exception
	 */
	private void updateLoopThroughGTask() throws Exception {
		ArrayList<ScheduleEvent> googleTaskItemsToDelete = new ArrayList<ScheduleEvent>();
		googleTaskItems = this.getGoogleTaskItems().getGoogleCalendarItems();
		if (googleTaskItems != null) {
			for (int i = 0; i < googleTaskItems.length; i++) {
				ScheduleEvent googleScheduleEvent = googleTaskItems[i];
				if (googleScheduleEvent.getUID() == null) {
					if (googleScheduleEvent.getName().equals(STRING_EMPTY)) continue;
					AppLogger.log(eventsList.addEvent(googleScheduleEvent));
					updateGoogleTaskListEvent(googleScheduleEvent);
				} else {
					ScheduleEvent localScheduleEvent = getEventByGoogleID(googleScheduleEvent.getGoogleUID()); 
					if (localScheduleEvent == null || localScheduleEvent.getType() == EVENT_TYPE_TIMED) {
						AppLogger.log(String.format(LOGGER_NEED_TO_DELETE_ITEM, googleScheduleEvent.getName()));
						googleTaskItemsToDelete.add(googleScheduleEvent);
					}
				}
			}
			
			for (int i = 0; i < googleTaskItemsToDelete.size(); i++) {
				deleteGoogleTaskListItem(googleTaskItemsToDelete.get(i).getGoogleUID());
			}
		}
	}

	/**
	 * Loops through the clone of the LifeShelf Storage and returns a ScheduleEvent with a 
	 * google UID that corresponds with the input googleUID
	 * @param googleUID - Google UID to search for
	 * @return ScheduleEvent with corresponding Google UID or null if not found
	 */
	private ScheduleEvent getEventByGoogleID(String googleUID) {
		for (int i = 0; i < cloneOfStorage.size(); i++) {
			ScheduleEvent eventToCheck = cloneOfStorage.get(i); 
			if (eventToCheck.getGoogleUID().equals(googleUID)) return eventToCheck;
		}
		return null;
	}

	/**
	 * Updates a localScheduleEvent with a googleCalScheduleEvent retrieved from Google Calendar API.
	 * @param localScheduleEvent
	 * @param googleCalScheduleEvent
	 */
	private void updateLocalEvent(ScheduleEvent localScheduleEvent,
			ScheduleEvent googleCalScheduleEvent) {
		eventsList.deleteEvent(eventsList.getEventIndex(localScheduleEvent)+1);
		googleCalScheduleEvent.isUpdated();
		eventsList.addEvent(googleCalScheduleEvent);
	}
	
	/**
	 * Extracts the updated time from a ScheduleEvent as a DateTime object
	 * @param scheduleEvent
	 * @return DateTime object which is the updated time
	 */
	private DateTime getUpdatedTime(ScheduleEvent scheduleEvent) {
		try {
			return DateTimeFormat.forPattern(DATETIME_FORMAT_GCAL_DATETIME).parseDateTime(scheduleEvent.getUpdated());
		} catch (Exception e) {
			return DateTimeFormat.forPattern(DATETIME_FORMAT_GTASK_DATETIME).parseDateTime(scheduleEvent.getUpdated());
		}
	}
	
	
	//TODO Start of Google CAL Methods
	
	/**
	 * Checks whether a calendar exists with the current googleCalendarLifeShelfID.
	 * @return responseCode of check
	 * @throws Exception
	 */
	private int checkExistenceOfCalendar() throws Exception {
		String url = String.format(GOOGLE_GET_CALENDAR_URL, googleCalendarLifeShelfID);
		 
		URL obj = new URL(url);
		HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
 
		// optional default is GET
		con.setRequestMethod(REQUEST_TYPE_GET);
 
		//add request header
		con.setRequestProperty(REQUEST_PROPERTY_AUTH, String.format(REQUEST_AUTH_INPUT ,accessToken));
 
		int responseCode = con.getResponseCode();
		AppLogger.log(String.format(LOGGER_CHECK_CALENDAR_RESPONSE_CODE, responseCode));
		return responseCode;
	}

	/**
	 * Gets an array of ScheduleEvent's from Google Calendar Database
	 * @return googleCalendarEventsListResponse
	 * @throws Exception
	 */
	private GoogleAPIResponse getCalendarItems() throws Exception {
		String url = String.format(GOOGLE_LIFESHELF_URL, googleCalendarLifeShelfID);
 
		URL obj = new URL(url);
		HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
 
		// optional default is GET
		con.setRequestMethod(REQUEST_TYPE_GET);
 
		//add request header
		con.setRequestProperty(REQUEST_PROPERTY_AUTH, String.format(REQUEST_AUTH_INPUT ,accessToken));
 
		int responseCode = con.getResponseCode();
		AppLogger.log(String.format(LOGGER_GET_CALENDAR_ITEMS_RESPONSE_CODE, responseCode));
 
		
		final GsonBuilder gsonBuilder = new GsonBuilder();
	    gsonBuilder.registerTypeAdapter(GoogleAPIResponse.class, new GoogleCalendarResponseDeserializer());
	    gsonBuilder.registerTypeAdapter(ScheduleEvent.class, new GoogleCalendarItemDeserializer());
	    final Gson gson = gsonBuilder.create();
	    
	    try (Reader reader = new InputStreamReader(con.getInputStream(), ENCODING_UTF8)) {
	        // Parse JSON to Java
	        final GoogleAPIResponse googleCalendarEventsListResponse = gson.fromJson(reader, GoogleAPIResponse.class);
	        return (googleCalendarEventsListResponse);
	    }
	}
	
	/**
	 * Creates a new Google Calendar on the Google Calendar API and sets the ID of the new
	 * Calendar in googleCalendarLifeShelfID
	 * @throws Exception
	 */
	private void createLifeShelfGoogleCalendar() throws Exception {

		String url = GOOGLE_CALENDAR_CREATE_CAL;
		URL obj = new URL(url);
		HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
		
		//add request header
		con.setRequestMethod(REQUEST_TYPE_POST);
		con.setRequestProperty(REQUEST_CONTENT_TYPE, REQUEST_CONTENT_TYPE_JSON);
		con.setRequestProperty(REQUEST_PROPERTY_AUTH, String.format(REQUEST_AUTH_INPUT ,accessToken));
 
		String urlParameters = String.format(GOOGLE_CALENDAR_CREATE_CAL_QUERY);
 
		// Send post request
		con.setDoOutput(true);
		DataOutputStream wr = new DataOutputStream(con.getOutputStream());
		wr.writeBytes(urlParameters);
		wr.flush();
		wr.close();
 
		int responseCode = con.getResponseCode();
		AppLogger.log(String.format(LOGGER_CREATE_NEW_LIFESHELF_CAL_RESPONSE_CODE, responseCode));
 
		Reader reader = new InputStreamReader(con.getInputStream());
		
		Gson gson = new GsonBuilder().create();
        GoogleCalendarCal response = gson.fromJson(reader, GoogleCalendarCal.class);
        this.googleCalendarLifeShelfID = response.getID();
        GoogleAPIInternalStorage.saveGoogleCalID(response.getID());
	}
	
	/**
	 * Loops through the retrieved Google Calendar Events and returns a ScheduleEvent with a 
	 * local UID that corresponds with the input localUID
	 * @param localID
	 * @return ScheduleEvent with corresponding local UID or null if not found
	 */
	private ScheduleEvent getGoogleCalItemById(String localID) {
		if (googleCalendarItems == null) return null;
		for (int i = 0; i < googleCalendarItems.length; i++) {
			String googleItemLocalID = googleCalendarItems[i].getUID();
			if (googleItemLocalID != null && googleItemLocalID.equals(localID)) { 
				return googleCalendarItems[i];
			}
		}
		return null;
	}
	
	/**
	 * Deletes a given Google Calendar Item by its google ID
	 * @param id - the Google ID of Item to delete
	 * @throws Exception
	 */
	private void deleteGoogleCalendarItem(String id) throws Exception {
		String url = String.format(GOOGLE_DELETE_ITEM_URL, googleCalendarLifeShelfID, id);
		 
		URL obj = new URL(url);
		HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
 
		// optional default is GET
		con.setRequestMethod(REQUEST_TYPE_DELETE);
 
		//add request header
		con.setRequestProperty(REQUEST_PROPERTY_AUTH, String.format(REQUEST_AUTH_INPUT ,accessToken));
 
		int responseCode = con.getResponseCode();
		AppLogger.log(String.format(LOGGER_DELETE_GCAL_ITEM_RESPONSE_CODE,responseCode));
	}

	
	/**
	 * Adds an event to Google Calendar and returns the google UID.
	 * @param localScheduleEvent
	 * @return new Google Calendar Event google UID
	 * @throws Exception
	 */
	private String addEventToLifeShelfCalendar(ScheduleEvent localScheduleEvent) throws Exception {
		String url = String.format(GOOGLE_CALENDAR_INSERT_EVENT, googleCalendarLifeShelfID);
		URL obj = new URL(url);
		HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
		
		//add request header
		con.setRequestMethod(REQUEST_TYPE_POST);
		con.setRequestProperty(REQUEST_CONTENT_TYPE, REQUEST_CONTENT_TYPE_JSON);
		con.setRequestProperty(REQUEST_PROPERTY_AUTH, String.format(REQUEST_AUTH_INPUT ,accessToken));
 
		String urlParameters = localScheduleEvent.toJSON();
 
		// Send post request
		con.setDoOutput(true);
		DataOutputStream wr = new DataOutputStream(con.getOutputStream());
		wr.writeBytes(urlParameters);
		wr.flush();
		wr.close();
 
		int responseCode = con.getResponseCode();
		AppLogger.log(String.format(LOGGER_ADD_EVENT_TO_GCAL_RESPONSE_CODE, responseCode));
		
		if (responseCode == 200) {
			final GsonBuilder gsonBuilder = new GsonBuilder();
		    gsonBuilder.registerTypeAdapter(ScheduleEvent.class, new GoogleCalendarItemDeserializer());
		    final Gson gson = gsonBuilder.create();
		    try (Reader reader = new InputStreamReader(con.getInputStream(), ENCODING_UTF8)) {
		        // Parse JSON to Java
		        final ScheduleEvent googleResponse = gson.fromJson(reader, ScheduleEvent.class);
		        //AppLogger.log(googleResponse.toJSON());
		        return googleResponse.getGoogleUID();
		    }	
		}
		return null;
	}
	
	/**
	 * Updates an existing Google Calendar Event with a local one.
	 * @param localScheduleEvent
	 * @throws Exception
	 */
	private void updateGoogleCalendarEvent(ScheduleEvent localScheduleEvent) throws Exception {
		String url = String.format(GOOGLE_CALENDAR_UPDATE_EVENT, googleCalendarLifeShelfID, localScheduleEvent.getGoogleUID());
		URL obj = new URL(url);
		HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
		
		//add request header
		con.setRequestMethod(REQUEST_TYPE_PUT);
		con.setRequestProperty(REQUEST_CONTENT_TYPE, REQUEST_CONTENT_TYPE_JSON);
		con.setRequestProperty(REQUEST_PROPERTY_AUTH, String.format(REQUEST_AUTH_INPUT ,accessToken));
 
		String urlParameters = localScheduleEvent.toJSON();
 
		// Send post request
		con.setDoOutput(true);
		DataOutputStream wr = new DataOutputStream(con.getOutputStream());
		wr.writeBytes(urlParameters);
		wr.flush();
		wr.close();
 
		int responseCode = con.getResponseCode();
		AppLogger.log(String.format(LOGGER_UPDATE_GCAL_RESPONSE_CODE, responseCode));
	}
	
	//TODO Start of Google Tasks Methods
	
	/**
	 * Checks whether a task list exists with the current googleTasksLifeShelfID.
	 * @return responseCode of check
	 * @throws Exception
	 */
	private int checkExistenceOfTaskList() throws Exception {
		String url = String.format(GOOGLE_GET_TASKLIST_URL, googleTasksLifeShelfID);
		 
		URL obj = new URL(url);
		HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
 
		// optional default is GET
		con.setRequestMethod(REQUEST_TYPE_GET);
 
		//add request header
		con.setRequestProperty(REQUEST_PROPERTY_AUTH, String.format(REQUEST_AUTH_INPUT ,accessToken));
 
		int responseCode = con.getResponseCode();
		AppLogger.log(String.format(LOGGER_CHECK_EXISTENCE_OF_TASKLIST_RESPONSE_CODE, responseCode));
		return responseCode;
	}

	/**
	 * Gets an array of ScheduleEvent's from Google Tasks Database
	 * @return googleTaskListResponse
	 * @throws Exception
	 */
	private GoogleAPIResponse getGoogleTaskItems() throws Exception {
		String url = String.format(GOOGLE_TASKS_URL, googleTasksLifeShelfID);
 
		URL obj = new URL(url);
		HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
 
		// optional default is GET
		con.setRequestMethod(REQUEST_TYPE_GET);
 
		//add request header
		con.setRequestProperty(REQUEST_PROPERTY_AUTH, String.format(REQUEST_AUTH_INPUT ,accessToken));
 
		int responseCode = con.getResponseCode();
		AppLogger.log(String.format(LOGGER_GET_TASKLIST_RESPONSE_CODE, responseCode));
 
		
		final GsonBuilder gsonBuilder = new GsonBuilder();
	    gsonBuilder.registerTypeAdapter(GoogleAPIResponse.class, new GoogleTaskResponseDeserializer());
	    gsonBuilder.registerTypeAdapter(ScheduleEvent.class, new GoogleTaskItemDeserializer());
	    final Gson gson = gsonBuilder.create();
	    
	    try (Reader reader = new InputStreamReader(con.getInputStream(), ENCODING_UTF8)) {
	        // Parse JSON to Java
	        final GoogleAPIResponse googleTaskListResponse = gson.fromJson(reader, GoogleAPIResponse.class);
	        return (googleTaskListResponse);
	    }
	}
	
	/**
	 * Creates a new Google Tasks on the Google Tasks API and sets the ID of the new
	 * TaskList in googleTasksLifeShelfID
	 * @throws Exception
	 */
	private void createGoogleTaskList() throws Exception {

		String url = GOOGLE_TASK_CREATE_TASKLIST;
		URL obj = new URL(url);
		HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
		
		//add request header
		con.setRequestMethod(REQUEST_TYPE_POST);
		con.setRequestProperty(REQUEST_CONTENT_TYPE, REQUEST_CONTENT_TYPE_JSON);
		con.setRequestProperty(REQUEST_PROPERTY_AUTH, String.format(REQUEST_AUTH_INPUT ,accessToken));
 
		String urlParameters = String.format(GOOGLE_TASK_CREATE_TASKLIST_QUERY);
 
		// Send post request
		con.setDoOutput(true);
		DataOutputStream wr = new DataOutputStream(con.getOutputStream());
		wr.writeBytes(urlParameters);
		wr.flush();
		wr.close();
 
		int responseCode = con.getResponseCode();
		AppLogger.log(String.format(LOGGER_CREATE_LIFESHELF_TASKLIST_RESPONSE_CODE, responseCode));
 
		Reader reader = new InputStreamReader(con.getInputStream());
		
		Gson gson = new GsonBuilder().create();
        GoogleCalendarTaskList response = gson.fromJson(reader, GoogleCalendarTaskList.class);
        this.googleTasksLifeShelfID = response.getID();
        GoogleAPIInternalStorage.saveGoogleTaskID(response.getID());
	}
	

	/**
	 * Loops through the retrieved Google Tasks List and returns a ScheduleEvent with a 
	 * local UID that corresponds with the input localUID
	 * @param localID
	 * @return ScheduleEvent with corresponding local UID or null if not found
	 */
	private ScheduleEvent getGoogleTaskItemById(String localID) {
		if (googleTaskItems == null) return null;
		for (int i = 0; i < googleTaskItems.length; i++) {
			String googleItemLocalID = googleTaskItems[i].getUID();
			if (googleItemLocalID != null && googleItemLocalID.equals(localID)) { 
				return googleTaskItems[i];
			}
		}
		return null;
	}
	
	
	/**
	 * Deletes a given Google TaskList Item by its google ID
	 * @param id - the Google ID of Item to delete
	 * @throws Exception
	 */
	private void deleteGoogleTaskListItem(String id) throws Exception {
		String url = String.format(GOOGLE_TASK_DELETE_ITEM_URL, googleTasksLifeShelfID, id);
		 
		URL obj = new URL(url);
		HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
 
		// optional default is GET
		con.setRequestMethod(REQUEST_TYPE_DELETE);
 
		//add request header
		con.setRequestProperty(REQUEST_PROPERTY_AUTH, String.format(REQUEST_AUTH_INPUT ,accessToken));
 
		int responseCode = con.getResponseCode();
		AppLogger.log(String.format(LOGGER_DELETE_GTASK_ITEM_RESPONSE_CODE, responseCode));
	}

	/**
	 * Adds an event to Google Tasks and returns the google UID.
	 * @param localScheduleEvent
	 * @return new Google Tasks Item google UID
	 * @throws Exception
	 */
	private String addEventToLifeShelfTaskList(ScheduleEvent localScheduleEvent) throws Exception {
		String url = String.format(GOOGLE_TASK_INSERT_TASK, googleTasksLifeShelfID);
		URL obj = new URL(url);
		HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
		
		//add request header
		con.setRequestMethod(REQUEST_TYPE_POST);
		con.setRequestProperty(REQUEST_CONTENT_TYPE, REQUEST_CONTENT_TYPE_JSON);
		con.setRequestProperty(REQUEST_PROPERTY_AUTH, String.format(REQUEST_AUTH_INPUT ,accessToken));
 
		String urlParameters = localScheduleEvent.toJSON();
 
		// Send post request
		con.setDoOutput(true);
		DataOutputStream wr = new DataOutputStream(con.getOutputStream());
		wr.writeBytes(urlParameters);
		wr.flush();
		wr.close();
 
		int responseCode = con.getResponseCode();
		AppLogger.log(String.format(LOGGER_ADD_EVENT_TO_GTASKS_RESPONSE_CODE, responseCode));
		
		if (responseCode == 200) {
			final GsonBuilder gsonBuilder = new GsonBuilder();
		    gsonBuilder.registerTypeAdapter(ScheduleEvent.class, new GoogleTaskItemDeserializer());
		    final Gson gson = gsonBuilder.create();
		    try (Reader reader = new InputStreamReader(con.getInputStream(), ENCODING_UTF8)) {
		        final ScheduleEvent googleResponse = gson.fromJson(reader, ScheduleEvent.class);
		        return googleResponse.getGoogleUID();
		    }	
		}
		return null;
	}
	
	/**
	 * Updates an existing Google Task Item with a local one.
	 * @param localScheduleEvent
	 * @throws Exception
	 */
	private void updateGoogleTaskListEvent(ScheduleEvent localScheduleEvent) throws Exception {
		String url = String.format(GOOGLE_TASK_UPDATE_TASK, googleTasksLifeShelfID, localScheduleEvent.getGoogleUID());
		URL obj = new URL(url);
		HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
		
		//add request header
		con.setRequestMethod(REQUEST_TYPE_PUT);
		con.setRequestProperty(REQUEST_CONTENT_TYPE, REQUEST_CONTENT_TYPE_JSON);
		con.setRequestProperty(REQUEST_PROPERTY_AUTH, String.format(REQUEST_AUTH_INPUT ,accessToken));
 
		String urlParameters = localScheduleEvent.toJSON();
 
		// Send post request
		con.setDoOutput(true);
		DataOutputStream wr = new DataOutputStream(con.getOutputStream());
		wr.writeBytes(urlParameters);
		wr.flush();
		wr.close();
 
		int responseCode = con.getResponseCode();
		AppLogger.log(String.format(LOGGER_UPDATE_GTASK_RESPONSE_CODE, responseCode));
	}
}